using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace CreamXStudio.XNARenderer
{

    public class PrimitiveBatch : IDisposable
    {
        #region Constants and Fields

        // this constant controls how large the vertices buffer is. Larger buffers will
        // require flushing less often, which can increase performance. However, having
        // buffer that is unnecessarily large will waste memory.
        const int DefaultBufferSize = 500;

        // a block of vertices that calling AddVertex will fill. Flush will draw using
        // this array, and will determine how many primitives to draw from
        // positionInBuffer.
        VertexPositionColor[] vertices = new VertexPositionColor[DefaultBufferSize];

        // keeps track of how many vertices have been added. this value increases until
        // we run out of space in the buffer, at which time Flush is automatically
        // called.
        int positionInBuffer = 0;

        // the vertex declaration that will be set on the device for drawing. this is 
        // created automatically using VertexPositionColor's vertex elements.
        VertexDeclaration vertexDeclaration;

        // a basic effect, which contains the shaders that we will use to draw our
        // primitives.
        BasicEffect basicEffect;

        // the device that we will issue draw calls to.
        Microsoft.Xna.Framework.Graphics.GraphicsDevice device;

        // this value is set by Begin, and is the type of primitives that we are
        // drawing.
        PrimitiveType primitiveType;

        // how many verts does each of these primitives take up? points are 1,
        // lines are 2, and triangles are 3.
        int numVertsPerPrimitive;

        // hasBegun is flipped to true once Begin is called, and is used to make
        // sure users don't call End before Begin is called.
        bool hasBegun = false;

        bool isDisposed = false;

        #endregion

        // the constructor creates a new PrimitiveBatch and sets up all of the internals
        // that PrimitiveBatch will need.
        public PrimitiveBatch(Microsoft.Xna.Framework.Graphics.GraphicsDevice graphicsDevice)
        {
            if (graphicsDevice == null)
            {
                throw new ArgumentNullException("graphicsDevice");
            }
            device = graphicsDevice;

            // create a vertex declaration, which tells the graphics card what kind of
            // data to expect during a draw call. We're drawing using
            // VertexPositionColors, so we'll use those vertex elements.
            vertexDeclaration = new VertexDeclaration(graphicsDevice,
                VertexPositionColor.VertexElements);

            // set up a new basic effect, and enable vertex colors.
            basicEffect = new BasicEffect(graphicsDevice, null);
            basicEffect.VertexColorEnabled = true;

            // projection uses CreateOrthographicOffCenter to create 2d projection
            // matrix with 0,0 in the upper left.
            basicEffect.Projection = Microsoft.Xna.Framework.Matrix.CreateOrthographicOffCenter
                (0, graphicsDevice.Viewport.Width,
                graphicsDevice.Viewport.Height, 0,
                0, 1);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && !isDisposed)
            {
                if (vertexDeclaration != null)
                    vertexDeclaration.Dispose();

                if (basicEffect != null)
                    basicEffect.Dispose();

                isDisposed = true;
            }
        }

        // Begin is called to tell the PrimitiveBatch what kind of primitives will be
        // drawn, and to prepare the graphics card to render those primitives.
        public void Begin(PrimitiveType primitiveType)
        {
            if (hasBegun)
            {
                throw new InvalidOperationException
                    ("End must be called before Begin can be called again.");
            }

            // these three types reuse vertices, so we can't flush properly without more
            // complex logic. Since that's a bit too complicated for this sample, we'll
            // simply disallow them.
            if (primitiveType == PrimitiveType.LineStrip ||
                primitiveType == PrimitiveType.TriangleFan ||
                primitiveType == PrimitiveType.TriangleStrip)
            {
                //throw new NotSupportedException
                //  ("The specified primitiveType is not supported by PrimitiveBatch.");
            }

            this.primitiveType = primitiveType;

            // how many verts will each of these primitives require?
            this.numVertsPerPrimitive = NumVertsPerPrimitive(primitiveType);

            // prepare the graphics device for drawing by setting the vertex declaration
            // and telling our basic effect to begin.
            device.VertexDeclaration = vertexDeclaration;
            basicEffect.Begin();
            basicEffect.CurrentTechnique.Passes[0].Begin();
            basicEffect.Projection = Microsoft.Xna.Framework.Matrix.CreateOrthographicOffCenter
               (0, this.device.Viewport.Width,
               device.Viewport.Height, 0,
               0, 1);
            // flip the error checking boolean. It's now ok to call AddVertex, Flush,
            // and End.
            hasBegun = true;
        }

        // AddVertex is called to add another vertex to be rendered. To draw a point,
        // AddVertex must be called once. for lines, twice, and for triangles 3 times.
        // this function can only be called once begin has been called.
        // if there is not enough room in the vertices buffer, Flush is called
        // automatically.
        public void AddVertex(Microsoft.Xna.Framework.Vector2 vertex, Microsoft.Xna.Framework.Graphics.Color color)
        {
            if (!hasBegun)
            {
                throw new InvalidOperationException
                    ("Begin must be called before AddVertex can be called.");
            }

            // are we starting a new primitive? if so, and there will not be enough room
            // for a whole primitive, flush.
            bool newPrimitive = ((positionInBuffer % numVertsPerPrimitive) == 0);

            if (newPrimitive &&
                (positionInBuffer + numVertsPerPrimitive) >= vertices.Length)
            {
                Flush();
            }

            // once we know there's enough room, set the vertex in the buffer,
            // and increase position.
            vertices[positionInBuffer].Position = new Microsoft.Xna.Framework.Vector3(vertex, 0);
            vertices[positionInBuffer].Color = color;

            positionInBuffer++;
        }

        // End is called once all the primitives have been drawn using AddVertex.
        // it will call Flush to actually submit the draw call to the graphics card, and
        // then tell the basic effect to end.
        public void End()
        {
            if (!hasBegun)
            {
                throw new InvalidOperationException
                    ("Begin must be called before End can be called.");
            }

            // Draw whatever the user wanted us to draw
            Flush();

            // and then tell basic effect that we're done.
            basicEffect.CurrentTechnique.Passes[0].End();
            basicEffect.End();
            hasBegun = false;
        }

        // Flush is called to issue the draw call to the graphics card. Once the draw
        // call is made, positionInBuffer is reset, so that AddVertex can start over
        // at the beginning. End will call this to draw the primitives that the user
        // requested, and AddVertex will call this if there is not enough room in the
        // buffer.
        private void Flush()
        {
            if (!hasBegun)
            {
                throw new InvalidOperationException
                    ("Begin must be called before Flush can be called.");
            }

            // no work to do
            if (positionInBuffer == 0)
            {
                return;
            }

            // how many primitives will we draw?
            int primitiveCount = positionInBuffer / numVertsPerPrimitive;

            // submit the draw call to the graphics card
            device.DrawUserPrimitives<VertexPositionColor>(primitiveType, vertices, 0,
                primitiveCount);

            // now that we've drawn, it's ok to reset positionInBuffer back to zero,
            // and write over any vertices that may have been set previously.
            positionInBuffer = 0;
        }

        #region Helper functions

        // NumVertsPerPrimitive is a boring helper function that tells how many vertices
        // it will take to draw each kind of primitive.
        static private int NumVertsPerPrimitive(PrimitiveType primitive)
        {
            int numVertsPerPrimitive;
            switch (primitive)
            {
                case PrimitiveType.PointList:
                    numVertsPerPrimitive = 1;
                    break;
                case PrimitiveType.LineList:
                    numVertsPerPrimitive = 2;
                    break;
                case PrimitiveType.TriangleList:
                    numVertsPerPrimitive = 3;
                    break;
                case PrimitiveType.LineStrip:
                    numVertsPerPrimitive = 2;
                    break;
                default:
                    throw new InvalidOperationException("primitive is not valid");
            }
            return numVertsPerPrimitive;
        }

        public void DrawLine(Microsoft.Xna.Framework.Vector2 point1, Microsoft.Xna.Framework.Vector2 point2, Microsoft.Xna.Framework.Graphics.Color color)
        {

            // tell the primitive batch to start drawing lines
            Begin(PrimitiveType.LineList);

            // from the nose, down the left hand side
            AddVertex(point1, color);
            AddVertex(point2, color);

            // and we're done.
            End();
        }
        public void DrawBox(Microsoft.Xna.Framework.Vector2 topLeft, Microsoft.Xna.Framework.Vector2 bottomRight)
        {
            // tell the primitive batch to start drawing lines
            Begin(PrimitiveType.LineList);

            // from the nose, down the left hand side
            AddVertex(
                topLeft, Microsoft.Xna.Framework.Graphics.Color.White);
            AddVertex(
                new Microsoft.Xna.Framework.Vector2(bottomRight.X, topLeft.Y), Microsoft.Xna.Framework.Graphics.Color.White);

            AddVertex(
                new Microsoft.Xna.Framework.Vector2(bottomRight.X, topLeft.Y), Microsoft.Xna.Framework.Graphics.Color.White);
            AddVertex(bottomRight, Microsoft.Xna.Framework.Graphics.Color.White);

            AddVertex(bottomRight, Microsoft.Xna.Framework.Graphics.Color.White);
            AddVertex(new Microsoft.Xna.Framework.Vector2(topLeft.X, bottomRight.Y), Microsoft.Xna.Framework.Graphics.Color.White);

            AddVertex(new Microsoft.Xna.Framework.Vector2(topLeft.X, bottomRight.Y), Microsoft.Xna.Framework.Graphics.Color.White);
            AddVertex(topLeft, Microsoft.Xna.Framework.Graphics.Color.White);

            // and we're done.
            End();
        }


        public void DrawBox(Microsoft.Xna.Framework.Vector2 topLeft, Microsoft.Xna.Framework.Vector2 bottomRight, Microsoft.Xna.Framework.Graphics.Color outline)
        {
            // tell the primitive batch to start drawing lines
            Begin(PrimitiveType.LineList);

            // from the nose, down the left hand side
            AddVertex(
                topLeft, outline);
            AddVertex(
                new Microsoft.Xna.Framework.Vector2(bottomRight.X, topLeft.Y), outline);

            AddVertex(
                new Microsoft.Xna.Framework.Vector2(bottomRight.X, topLeft.Y), outline);
            AddVertex(bottomRight, outline);

            AddVertex(bottomRight, outline);
            AddVertex(new Microsoft.Xna.Framework.Vector2(topLeft.X, bottomRight.Y), outline);

            AddVertex(new Microsoft.Xna.Framework.Vector2(topLeft.X, bottomRight.Y), outline);
            AddVertex(topLeft, outline);

            // and we're done.
            End();
        }
        int defaultPointCountForCircle = 24;
        public void DrawCircle(Microsoft.Xna.Framework.Vector2 center, float radius, Microsoft.Xna.Framework.Graphics.Color color)
        {

            Begin(PrimitiveType.LineList);

            double angle = MathHelper.TwoPi / defaultPointCountForCircle;
            for (int i = 1; i <= defaultPointCountForCircle; i++)
            {
                AddVertex(center + new Microsoft.Xna.Framework.Vector2(
                                        (float)Math.Round(Math.Sin(angle * i), 4) * radius,
                                        (float)Math.Round(Math.Cos(angle * i), 4) * radius),
                                        color);

                AddVertex(center + new Microsoft.Xna.Framework.Vector2(
                                        (float)Math.Round(Math.Sin(angle * (i + 1)), 4) * radius,
                                        (float)Math.Round(Math.Cos(angle * (i + 1)), 4) * radius),
                                        color);
            }

            // and we're done.
            End();
        }
        public void DrawPoints(Microsoft.Xna.Framework.Vector2[] points)
        {

            Begin(PrimitiveType.PointList);


            for (int i = 1; i <= points.Length; i++)
            {
                AddVertex(points[i], Microsoft.Xna.Framework.Graphics.Color.White);
            }

            // and we're done.
            End();
        }


        #endregion


    }
}
